/*
* Copyright 2005-2008 Pulse Technologies. Tutti i diritti riservati.
*/
package org.obsidianrad.server.services.obj2db;


import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import javolution.util.FastMap;

import org.obsidianrad.server.services.ObsModel;
import org.obsidianrad.server.services.obj2db.commodities.Filterer;
import org.obsidianrad.server.services.obj2db.commodities.PagingLoadConfig;
import org.obsidianrad.server.services.obj2db.commodities.PagingLoadResult;
import org.obsidianrad.server.services.obj2db.dao.GenericProxy;
import org.obsidianrad.server.services.obj2db.decisioners.IDecisioner;
import org.obsidianrad.utils.ObjProxyException;
import org.obsidianrad.utils.ObjProxyUserAccessException;
import org.obsidianrad.utils.Token;
import org.obsidianrad.utils.TokenFactory;
import org.obsidianrad.utils.Constant.ObsidianOperations;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.security.AccessDeniedException;

/**
 * Servizio generico per il recupero di dati dal backend.
 * Fa uso delle classi che 
 * @author eliosh
 * @version $Rev: 90 $
 * @lastautorcommit $Author: eliosh $
 * @lastdatecommit $Date: 2010-02-03 08:06:51 +0100(mer, 03 feb 2010) $
 */

public final class Obj2DbProxyImpl extends ApplicationObjectSupport implements Obj2DbProxy {
	
	private FastMap<String, String> classBeanMapper; 
	private static final Logger log = Logger.getLogger(Obj2DbProxyImpl.class.getCanonicalName());
	private static final TokenFactory factory = TokenFactory.getInstance();
	private IDecisioner decisioner = null;
	
	/**
	 * Restituisce il bean in spring capace di trattare l'oggetto passato come parametro.
	 * Provvede a creare una mappa per associare ad un discendente della classe ObsModel un bean in spring capace di trattare tale oggetto, 
	 * se non esistente.
	 * 
	 * @param reference Oggetto da trattare
	 * @return Il generico proxy capace di gestire questo oggetto.
	 * @throws ObjProxyException Se un bean per gestire questo oggetto non esiste, solleva l'eccezione. 
	 */
	@SuppressWarnings("unchecked")
	private <T extends ObsModel> GenericProxy<T> getGenericProxy(String reference) throws ObjProxyException{
		// se la mappa è vuota, riempila.
		if(classBeanMapper==null){
			classBeanMapper = new FastMap<String, String>();
			
			//recupera la classe
			Class<?> beanType = GenericProxy.class;
			
			// recupera dall'application context tutte le classi che estendono il GenericProxyImpl 
			Map<String, GenericProxy> beansOfType = this.getApplicationContext().getBeansOfType(beanType);
			
			Set<String> beanNames = beansOfType.keySet();
			for (String beanName : beanNames) {
				GenericProxy genericProxy = beansOfType.get(beanName);
				classBeanMapper.put(genericProxy.getTypeClass(), beanName);
				log.finest("BeanName = " + beanName  + " | typeClass : " + genericProxy.getTypeClass());
			}
		}

		String beanName = classBeanMapper.get(reference);
		if(beanName!=null){
			return (GenericProxy) this.getApplicationContext().getBean(beanName);
		}else{
			// nessun bean capace di mappare questa classe.
			log.severe("Unable to find a bean capable to handle " + reference.getClass().getCanonicalName() + " class type.");
			throw new ObjProxyException("Unable to find a bean capable to handle " + reference.getClass().getCanonicalName() + " class type.");
		}
	}

	private <T extends ObsModel> GenericProxy<T> getGenericProxy(Class<? extends ObsModel> reference) throws ObjProxyException{
		return getGenericProxy(reference.getCanonicalName());
	}
	
	@Override
	public <T extends ObsModel> Integer deleteMany(Filterer reference) throws ObjProxyException {
		if(!decisioner.decide(ObsidianOperations.KEY_DELETE_MANY, reference.getTypeObject())){
			throw new ObjProxyUserAccessException("Utente non abilitato per questo contesto.");
		}
		GenericProxy<T> proxy = this.getGenericProxy(reference.getTypeObject());
		Integer deleteMany = proxy.deleteMany(reference);
		return deleteMany;
	}

	@Override
	public <T extends ObsModel>  void deleteOne(T reference, Token<T> token) throws ObjProxyException {
		if(!factory.isTokenValid(token)){
			throw new ObjProxyException("Token non valido");			
		}
		if(!decisioner.decide(ObsidianOperations.KEY_DELETE_ONE, reference.getClass())){
			throw new ObjProxyUserAccessException("Utente non abilitato per questo contesto.");
		}
		GenericProxy<T> proxy = this.getGenericProxy(reference.getClass());
		try{
			proxy.deleteOne(reference, token);
			proxy.obs_notify(reference, ObsidianOperations.KEY_DELETE_ONE);
		}catch(AccessDeniedException ex){
			throw new ObjProxyUserAccessException(ex);
		}catch (Exception ex){
			throw new ObjProxyException(ex);
		}
	}

	@Override
	public <T extends ObsModel> T getSingle(T reference) throws ObjProxyException {
		if(!decisioner.decide(ObsidianOperations.KEY_GET_SINGLE, reference.getClass())){
			throw new ObjProxyUserAccessException("Utente non abilitato per questo contesto.");
		}
		GenericProxy<T> proxy = this.getGenericProxy(reference.getClass());
		T ret = null;
		try{
			ret = proxy.getSingle(reference);
			proxy.obs_notify(reference, ObsidianOperations.KEY_GET_SINGLE);
		} catch(AccessDeniedException ex){
			throw new ObjProxyUserAccessException(ex);
		} catch (Exception ex){
			throw new ObjProxyException(ex);
		}
		return ret; //
	}

	@Override
	public <T extends ObsModel> PagingLoadResult<T> getListByExample(T reference, PagingLoadConfig config) throws ObjProxyException {
		if(!decisioner.decide(ObsidianOperations.KEY_GET_LIST, reference.getClass())){
			throw new ObjProxyUserAccessException("Utente non abilitato per questo contesto.");
		}
		GenericProxy<T> proxy = this.getGenericProxy(reference.getClass());
		PagingLoadResult<T> ret = null;
		try{
			ret = proxy.getList(reference, config);
			proxy.obs_notify(ret, ObsidianOperations.KEY_GET_LIST);
		} catch(AccessDeniedException ex){
			throw new ObjProxyUserAccessException(ex);
		} catch(Exception e){
			throw new ObjProxyException(e);			
		}
		return ret;
	}

	@Override
	public <T extends ObsModel> PagingLoadResult<T> getList(Filterer reference, PagingLoadConfig config) throws ObjProxyException {
		if(!decisioner.decide(ObsidianOperations.KEY_GET_LIST_FILTERED, reference.getTypeObject())){
			throw new ObjProxyUserAccessException("Utente non abilitato per questo contesto.");
		}
		GenericProxy<T> proxy = this.getGenericProxy(reference.getTypeObject());
		PagingLoadResult<T> ret = null;
		try{
			ret = proxy.getList(reference, config);
			proxy.obs_notify(ret, ObsidianOperations.KEY_GET_LIST_FILTERED);
		} catch(AccessDeniedException ex){
			throw new ObjProxyUserAccessException(ex);
		} catch(Exception e){
			throw new ObjProxyException(e);			
		}
		return ret;
	}

	@Override
	public <T extends ObsModel> String save(T reference) throws ObjProxyException {
		if(!decisioner.decide(ObsidianOperations.KEY_SAVE, reference.getClass())){
			throw new ObjProxyUserAccessException("Utente non abilitato per questo contesto.");
		}
		GenericProxy<T> proxy = this.getGenericProxy(reference.getClass());
		String ret = null;
		try{
			ret = proxy.save(reference);
			proxy.obs_notify(reference, ObsidianOperations.KEY_SAVE);
		} catch(AccessDeniedException ex){
			throw new ObjProxyUserAccessException(ex);
		} catch (Exception ex){
			throw new ObjProxyException(ex);
		}
		
		return ret;
	}

	@Override
	public <T extends ObsModel> T updateOne(T reference, Token<T> token) throws ObjProxyException {
		if(!decisioner.decide(ObsidianOperations.KEY_UPDATE_ONE, reference.getClass())){
			throw new ObjProxyUserAccessException("Utente non abilitato per questo contesto.");
		}
		if(!factory.isTokenValid(token)){
			throw new ObjProxyException("Token non valido");			
		}
		GenericProxy<T> proxy = this.getGenericProxy(reference.getClass());
		T ret = null;
		try{
			ret = (T) proxy.updateOne(reference, token);
			proxy.obs_notify(ret, ObsidianOperations.KEY_UPDATE_ONE);
		} catch(AccessDeniedException ex){
			throw new ObjProxyUserAccessException(ex);
		} catch (Exception e) {
			throw new ObjProxyException(e);
		}
		return ret;
	}

	@Override
	public <T extends ObsModel> int updateMany(Filterer reference) throws ObjProxyException {
		if(!decisioner.decide(ObsidianOperations.KEY_UPDATE_MANY, reference.getTypeObject())){
			throw new ObjProxyUserAccessException("Utente non abilitato per questo contesto.");
		}
		GenericProxy<T> proxy = this.getGenericProxy(reference.getTypeObject());
		Integer ret = null;
		try{
			ret = proxy.updateMany(reference);
//			proxy.obs_notify(ret, ObsidianOperations.KEY_UPDATE_MANY);
		} catch(AccessDeniedException ex){
			throw new ObjProxyUserAccessException(ex);
		} catch(Exception e){
			throw new ObjProxyException(e);
		}
		return ret;
	}

	@Override
	public <T extends ObsModel> void discardToken(Token<T> token) {
		factory.discardToken(token);
	}

	@Override
	public <T extends ObsModel> Token<T> getToken(T reference) {
		return factory.getToken(reference);
	}

	@Override
	public <T extends ObsModel> Token<T> refreshToken(Token<T> token) {
		return factory.refreshToken(token);
	}

	/**
	 * @return the decisioner
	 */
	public IDecisioner getDecisioner() {
		return decisioner;
	}

	/**
	 * @param decisioner the decisioner to set
	 */
	public void setDecisioner(IDecisioner decisioner) {
		this.decisioner = decisioner;
	}
	
}
